{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 遥控操作\n",
    "\n",
    "在此示例中，我们将使用连接到Web浏览器机器的游戏手柄控制器远程控制Jetbot。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  创建游戏手柄控制器\n",
    "\n",
    "我们要做的第一件事是创建一个Controller小部件的实例，我们将用它来驱动机器人。\n",
    "Controller控件带有一个index参数，该参数指定控制器的编号。 如果您\n",
    "附有多个控制器，或者某些游戏手柄“出现”为多个控制器。 确定指标\n",
    "您正在使用的控制器\n",
    "\n",
    "1.访问[http://html5gamepad.com]（http://html5gamepad.com）。     \n",
    "2.按下您正在使用的游戏板上的按钮          \n",
    "3.记住响应按钮按下的游戏手柄的“索引”           \n",
    "\n",
    "接下来，我们将使用该索引创建并显示我们的控制器。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import ipywidgets.widgets as widgets\n",
    "\n",
    "controller = widgets.Controller(index=1)  # replace with index of your controller\n",
    "\n",
    "display(controller)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "即使索引正确，您也可能会看到文字``连接游戏板并按任意按钮''。 那是因为游戏手柄还没有\n",
    "已在此笔记本上注册。 按下一个按钮，您应该看到游戏手柄小部件出现在上方。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  将游戏手柄控制器连接到机器人马达\n",
    "\n",
    "现在，即使我们已经连接了游戏手柄，也尚未将控件附加到机器人上！ 最简单的控制\n",
    "我们要附加的是电机控制。 我们将使用dlink函数将其连接到左右垂直轴。 的\n",
    "与``link''函数不同，``dlink''函数允许我们在``source''和``target''之间附加转换。 因为\n",
    "控制器轴从我们认为直观的电动机控制方向翻转了，我们将使用一个小的* lambda *函数\n",
    "取反值。\n",
    "\n",
    ">警告：如果触摸游戏手柄控制器轴，则下一个单元格将移动机器人！"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from jetbot import Robot\n",
    "import traitlets\n",
    "\n",
    "robot = Robot()\n",
    "\n",
    "left_link = traitlets.dlink((controller.axes[1], 'value'), (robot.left_motor, 'value'), transform=lambda x: -x)\n",
    "right_link = traitlets.dlink((controller.axes[3], 'value'), (robot.right_motor, 'value'), transform=lambda x: -x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "太棒了！ 现在，我们的机器人应该对游戏手柄控制器的动作做出响应。 现在，我们想从摄像机观看实时视频！"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  创建并显示图像小部件\n",
    "\n",
    "首先，让我们显示一个“ Image”小部件，我们将使用它来显示实时摄像机的提要。 我们将设置“高度”和“宽度”\n",
    "到300像素，因此不会占用太多空间。\n",
    "\n",
    ">仅供参考：高度和宽度仅影响浏览器端的渲染，而不影响从机器人到浏览器的网络传输之前的原始图像分辨率。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "image = widgets.Image(format='jpeg', width=300, height=300)\n",
    "\n",
    "display(image)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  创建相机实例\n",
    "\n",
    "好吧，目前没有图像，因为我们还没有设置值！ 我们可以通过创建相机来做到这一点\n",
    "类，并将相机的“ value”属性附加到图像的“ value”属性。\n",
    "\n",
    "首先，让我们创建相机实例，我们调用“ instance”方法来创建一个新相机\n",
    "如果尚未创建。 如果已经存在，则此方法将返回现有相机。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from jetbot import Camera\n",
    "\n",
    "camera = Camera.instance()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 将相机连接到图像小部件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们的相机类目前仅产生BGR8（蓝色，绿色，红色，8位）格式的值，而我们的图像小部件则接受压缩后的* JPEG *值。\n",
    "要将相机连接到图像，我们需要在链接中插入bgr8_to_jpeg函数作为转换。 我们在下面做"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from jetbot import bgr8_to_jpeg\n",
    "\n",
    "camera_link = traitlets.dlink((camera, 'value'), (image, 'value'), transform=bgr8_to_jpeg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "您现在应该看到上面显示的实时视频供稿！\n",
    "\n",
    ">提醒：您可以右键单击单元格的输出，然后选择“为输出创建新视图”以在单独的窗口中显示该单元格。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  如果网络断开，请停止机器人\n",
    "\n",
    "您可以通过浏览视频源来驱动机器人。 但是，如果您的机器人与Wifi断开连接该怎么办？ 好吧，电动机将继续运行，并且将继续尝试流式传输视频和电动机命令。 让我们做到这一点，以便在发生断开连接时停止机器人并断开相机和电机的链接。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from jetbot import Heartbeat\n",
    "\n",
    "\n",
    "def handle_heartbeat_status(change):\n",
    "    if change['new'] == Heartbeat.Status.dead:\n",
    "        camera_link.unlink()\n",
    "        left_link.unlink()\n",
    "        right_link.unlink()\n",
    "        robot.stop()\n",
    "\n",
    "heartbeat = Heartbeat(period=0.5)\n",
    "\n",
    "# attach the callback function to heartbeat status\n",
    "heartbeat.observe(handle_heartbeat_status, names='status')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果机器人断开互联网连接，您会注意到它停止了。 然后，您可以通过与下面的单元格重新创建链接来重新连接摄像机和电机"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# only call this if your robot links were unlinked, otherwise we'll have redundant links which will double\n",
    "# the commands transfered\n",
    "\n",
    "left_link = traitlets.dlink((controller.axes[1], 'value'), (robot.left_motor, 'value'), transform=lambda x: -x)\n",
    "right_link = traitlets.dlink((controller.axes[3], 'value'), (robot.right_motor, 'value'), transform=lambda x: -x)\n",
    "camera_link = traitlets.dlink((camera, 'value'), (image, 'value'), transform=bgr8_to_jpeg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  使用游戏手柄按钮保存快照\n",
    "\n",
    "现在，我们希望能够从机器人中保存一些图像。 让我们做一下，以便右保险杠（索引5）保存当前实时图像的快照。 我们将图像保存在“ snapshots /”目录中，并使用“ uuid” python软件包确保其名称唯一。 我们使用``uuid1``标识符，因为它还会对日期和MAC地址进行编码，以备日后使用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import uuid\n",
    "import subprocess\n",
    "\n",
    "subprocess.call(['mkdir', '-p', 'snapshots'])\n",
    "\n",
    "snapshot_image = widgets.Image(format='jpeg', width=300, height=300)\n",
    "\n",
    "def save_snapshot(change):\n",
    "    # save snapshot when button is pressed down\n",
    "    if change['new']:\n",
    "        file_path = 'snapshots/' + str(uuid.uuid1()) + '.jpg'\n",
    "        \n",
    "        # write snapshot to file (we use image value instead of camera because it's already in JPEG format)\n",
    "        with open(file_path, 'wb') as f:\n",
    "            f.write(image.value)\n",
    "            \n",
    "        # display snapshot that was saved\n",
    "        snapshot_image.value = image.value\n",
    "\n",
    "\n",
    "controller.buttons[5].observe(save_snapshot, names='value')\n",
    "\n",
    "display(widgets.HBox([image, snapshot_image]))\n",
    "display(controller)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  结论\n",
    "\n",
    "就是这个示例，玩得开心！"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
